﻿using System;
using System.ComponentModel;
using System.Reflection;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;

using Argos.Activities;
using Argos.Diagnostics;
using System.Diagnostics;

namespace Argos.Workflows
{
	/// <summary>
	/// Defines a state machine workflow that is used to monitor a target for failure.
	/// </summary>
	[DisplayName("Failure Monitor")]
	[Description("Monitors a target for failures and sends alerts when a treshold is exceeded.")]
    public sealed partial class TargetFailureMonitoringWorkflow : TargetMonitoringWorkflowBase , ITargetFailureMonitoringWorkflow
	{

		#region #ctor: public TargetFailureMonitoringWorkflow()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public TargetFailureMonitoringWorkflow()
		{
			InitializeComponent();

			this.AlertForChangedTestError = true;
			this.FailureAlertThreshold = 3;
			this.FailureReminderAlertInterval = TimeSpan.FromMinutes(10);
		}

		#endregion



		#region public bool AlertForChangedTestError { get; set; }

		public static readonly DependencyProperty AlertForChangedTestErrorProperty = DependencyProperty.Register("AlertForChangedTestError", typeof(bool), typeof(TargetFailureMonitoringWorkflow));

		[Category("Behaviour")]
		[DisplayName("Alert For Changed Error")]
		[Description("Specifies whether to send a new alert every time the test for the target produces a different error. When comparing errors, only the type and message of the error is checked.")]
		[DefaultValue(true)]
		public bool AlertForChangedTestError
		{
			get { return (bool)GetValue(AlertForChangedTestErrorProperty); }
			set { SetValue(AlertForChangedTestErrorProperty, value); }
		}

		#endregion

		#region public bool EnableReminderAlerts { get; set; }

		public static readonly DependencyProperty EnableReminderAlertsProperty =
			DependencyProperty.Register("EnableReminderAlerts", typeof(bool), typeof(TargetFailureMonitoringWorkflow));

		[Category("Behaviour")]
		[DisplayName("Enable Alert Reminders")]
		[Description("Specifies whether failure reminder alerts are sent for the target.")]
		[DefaultValue(false)]
		public bool EnableReminderAlerts
		{
			get { return (bool)GetValue(EnableReminderAlertsProperty); }
			set { SetValue(EnableReminderAlertsProperty, value); }
		}

		#endregion

		#region public TimeSpan FailureReminderAlertInterval { get; set; }

		public static readonly DependencyProperty FailureReminderAlertIntervalProperty = DependencyProperty.Register("FailureReminderAlertInterval", typeof(TimeSpan), typeof(TargetFailureMonitoringWorkflow));

		[Category("Behaviour")]
		[DisplayName("Alert Reminder Interval")]
		[Description("Specifies the interval between failure reminders alerts. A failure reminder alert is sent after this set interval has elapsed after the initial alert if the state of the target is still failed.")]
		[DefaultValue("00:10:00")]
		[TypeConverter(typeof(TimeSpanConverter))]
		public TimeSpan FailureReminderAlertInterval
		{
			get { return (TimeSpan)this.GetValue(FailureReminderAlertIntervalProperty); }
			set { this.SetValue(FailureReminderAlertIntervalProperty, value); }
		}

		#endregion
		
		#region public uint FailureAlertThreshold { get; set; }

		public static readonly DependencyProperty FailureAlertThresholdProperty = DependencyProperty.Register("FailureAlertThreshold", typeof(uint), typeof(TargetFailureMonitoringWorkflow));

		/// <summary>
		/// Sets or returns the number of consecutive failed tests will cause the
		/// state machine workflow to enter the Alerted state.
		/// </summary>
		[Category("Behaviour")]
		[Description("Specifies how many consecutive failures will cause the state machine workflow to enter the 'Alerted' state.")]
		[DisplayName("Alert Threshold")]
		[ValidationOption(ValidationOption.Optional)]
		[DefaultValue(3)]
		public uint FailureAlertThreshold
		{
			get { return (uint)this.GetValue(TargetFailureMonitoringWorkflow.FailureAlertThresholdProperty); }
			set { this.SetValue(TargetFailureMonitoringWorkflow.FailureAlertThresholdProperty, value); }
		}

		#endregion

		#region public uint FailureCount { get; set; }

		public static readonly DependencyProperty FailureCountProperty = DependencyProperty.Register("FailureCount", typeof(uint), typeof(TargetFailureMonitoringWorkflow));

		/// <summary>
		/// Sets or returns the number of consecutive failures that currently have been encountered in the state machine workflow.
		/// </summary>
		[Browsable(false)]
		[Description("Contains the number of consecutive errors encountered in the state machine workflow. This value is intended to be modified only from within the workflow.")]
		[ValidationOption(ValidationOption.None)]
		[DefaultValue(0)]
		public uint FailureCount
		{
			get { return (uint)this.GetValue(TargetFailureMonitoringWorkflow.FailureCountProperty); }
			set { this.SetValue(TargetFailureMonitoringWorkflow.FailureCountProperty, value); }
		}

		#endregion

		#region public Exception LastTestError { get; set; }

		public static readonly DependencyProperty LastTestErrorProperty = DependencyProperty.Register("LastTestError", typeof(Exception), typeof(TargetFailureMonitoringWorkflow));

		/// <summary>
		/// Sets or returns the last error encountered when executing the state machine workflow.
		/// </summary>
		[Browsable(false)]
		[Description("Contains the last exception that occured in the workflow instance.")]
		[DefaultValue(null)]
		public Exception LastTestError
		{
			get { return (Exception)this.GetValue(TargetFailureMonitoringWorkflow.LastTestErrorProperty); }
			set { this.SetValue(TargetFailureMonitoringWorkflow.LastTestErrorProperty, value); }
		}

		#endregion



		#region private DateTime? LastFailureAlertTimeStamp { get; set; }

		private static readonly DependencyProperty LastFailureAlertTimeStampProperty = DependencyProperty.Register("LastFailureAlertTimeStamp", typeof(DateTime?), typeof(TargetFailureMonitoringWorkflow));

		private DateTime? LastFailureAlertTimeStamp
		{
			get { return (DateTime?)this.GetValue(LastFailureAlertTimeStampProperty); }
			set { this.SetValue(LastFailureAlertTimeStampProperty, value); }
		}

		#endregion

		#region private Exception PreviousTestError { get; set; }

		private static readonly DependencyProperty PreviousTestErrorProperty = DependencyProperty.Register("PreviousTestError", typeof(Exception), typeof(TargetFailureMonitoringWorkflow));

		private Exception PreviousTestError
		{
			get { return (Exception)GetValue(PreviousTestErrorProperty); }
			set { SetValue(PreviousTestErrorProperty, value); }
		}

		#endregion



		private void Condition_HasAlertBeenSent(object sender, ConditionalEventArgs e)
		{
			e.Result = this.LastFailureAlertTimeStamp.HasValue;
		}

		private void Condition_IsNewFailureAlertRequired(object sender, ConditionalEventArgs e)
		{
			e.Result = false;

			if (this.FailureAlertThreshold <= this.FailureCount)
			{
				// No alerts are never sent if the alert threshold has not been exceeded.

				if (this.FailureAlertThreshold == this.FailureCount)
				{
					// If this is the first time the threshold exceeds, we always send
					// an alert, no matter what.
					e.Result = true;
				}
				else if (this.EnableReminderAlerts && this.LastFailureAlertTimeStamp.HasValue && this.LastFailureAlertTimeStamp.Value.ToUniversalTime().Add(this.FailureReminderAlertInterval) < DateTime.UtcNow)
				{
					// If failure reminders are enabled, and the specified reminder interval
					// has elapsed, we send an alert.
					e.Result = true;
				}
				else if(this.AlertForChangedTestError && null != this.LastTestError && null != this.PreviousTestError && (!object.Equals(this.LastTestError.GetType(), this.PreviousTestError.GetType()) || !object.Equals(this.LastTestError.Message, this.PreviousTestError.Message)))
				{
					// If alerting for changed errors is enabled, and the error has changed,
					// we send an alert.
					e.Result = true;
				}
			}
		}

		private void Condition_IsTargetDisabled(object sender, ConditionalEventArgs e)
		{
			e.Result = ((ITargetMonitoringWorkflow)this).TargetDisabled;
			if (e.Result)
			{
				Tracer.TraceData(TraceEventType.Warning, 0, "Monitoring is disabled for current target.", Tracer.CreateData("TargetId", this.TargetId), Tracer.CreateData("InstanceId", this.WorkflowInstanceId));
			}
		}

		private void Condition_IsTargetPaused(object sender, ConditionalEventArgs e)
		{
			e.Result = this.IsPaused();
			if (e.Result)
			{
				Tracer.TraceData(TraceEventType.Warning, 0, "Monitoring is paused for current target.", Tracer.CreateData("TargetId", this.TargetId), Tracer.CreateData("InstanceId", this.WorkflowInstanceId));
			}
		}

		private void Condition_IsTimeForReminderAlert(object sender, ConditionalEventArgs e)
		{
			e.Result = 
				this.EnableReminderAlerts
				&& this.LastFailureAlertTimeStamp.HasValue
				&& this.LastFailureAlertTimeStamp.Value.ToUniversalTime().Add(this.FailureReminderAlertInterval) < DateTime.UtcNow;
		}

		private void Condition_IsAlertCountGreaterThanZero(object sender, ConditionalEventArgs e)
		{
			e.Result = this.FailureCount > 0;
		}

		private void Condition_IsAlertThresholdExceeded(object sender, ConditionalEventArgs e)
		{
			e.Result = this.FailureAlertThreshold <= this.FailureCount;
		}

		private void OnInitializeTestDelay(object sender, EventArgs e)
		{
			this.TestDelay1.TimeoutDuration = ((ITargetMonitoringWorkflow)this).TestInterval;
			this.TestDelay2.TimeoutDuration = this.TestDelay1.TimeoutDuration;
			this.TestDelay3.TimeoutDuration = this.TestDelay1.TimeoutDuration;
		}

		private void SetFailureAlertTimeStamp_ExecuteCode(object sender, EventArgs e)
		{
			this.LastFailureAlertTimeStamp = DateTime.UtcNow;
		}

		private void ClearFailureAlertTimeStamp_ExecuteCode(object sender, EventArgs e)
		{
			this.LastFailureAlertTimeStamp = null;
		}



		void ITargetFailureMonitoringWorkflow.HandleTestFailure(Exception ex)
		{
			this.PreviousTestError = this.LastTestError;
			this.LastTestError = ex;
			this.FailureCount++;
		}

		void ITargetFailureMonitoringWorkflow.HandleTestSuccess()
		{
			this.LastTestError = null;
			this.PreviousTestError = null;
			this.FailureCount = 0;
		}

    }
}
